#include "inout_track.h"

#include <opencv2/opencv.hpp>
#include <cstdlib>
#include <chrono>
#include <codecvt>

#include "sdc_sys_res.h"
#include "hw_app_config.h"
#include "image_cache.h"
#include "polyiou.h"
#include "md5.h"
#include "levenshtein.hpp"
#include "CvxText.h"
#include "lpr.h"
#include "middleware.h"

namespace gc
{
    void compressImageWithROI(cv::Mat& image, const cv::Rect& roi, int highQuality = 80, int lowQuality = 8) {
        // 检查ROI区域是否合法
        if (roi.x < 0 || roi.y < 0 || roi.x + roi.width > image.cols || roi.y + roi.height > image.rows) {
            std::cerr << "Invalid ROI region." << std::endl;
            return;
        }

        // 创建高质量的ROI图像
        cv::Mat highQualityROI = image(roi).clone();

        // 压缩整体图像为低质量
        std::vector<uchar> buf;
        cv::imencode(".jpg", image, buf, {cv::IMWRITE_JPEG_QUALITY, lowQuality});
        cv::Mat lowQualityImage = cv::imdecode(buf, cv::IMREAD_COLOR);

        // 确保低质量图像的尺寸与原图一致
        if (lowQualityImage.size() != image.size()) {
            std::cerr << "Size mismatch after compression." << std::endl;
            return;
        }

        // 将高质量ROI粘贴回低质量图像
        highQualityROI.copyTo(lowQualityImage(roi));

        // 直接替换原始图像的内容
        lowQualityImage.copyTo(image);

        highQualityROI.release();
        lowQualityImage.release();
    }

    InoutTrack::InoutTrack(std::string camera_key, int track_id): m_track_end(false), m_dirty(false), m_start_pos_side(-1), m_drop_track_obj_coutinuous_count(0), m_drop_reason(DropReason::DR_INIT)
    {
        m_camera_key = camera_key;
        m_track_id = track_id;
        m_start_time = std::time(nullptr);
        m_last_track_obj_time = m_start_time;
        m_track_result.camera_key = m_camera_key;
        m_track_result.track_id = m_track_id;
        m_track_result.inout = InoutType::INIT;
        m_track_objs.reserve(64);
    }

    InoutTrack::~InoutTrack(void)
    {
    }

    bool InoutTrack::AddTrackObj(GCTrackObj &trackObj)
    {
        int max_continuous_drop_frame_time = 3; // TODO configable 最大相同原因连续丢弃帧数
        m_last_track_obj_time = std::time(nullptr);
        if (m_track_objs.size() == 0) {
            m_1st_track_obj = trackObj;
            m_start_pos_side = IsPointInPolygon(m_1st_track_obj.cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1; // 0 矩形外；1 矩形内
        } else if (gc_app_config.camera_config_map[m_camera_key].style != GC_CAMERA_STYLE::STYLE_BAN) {
            // 过滤掉检测框大于等于上一帧N倍或小于等于上一帧1/N的目标
            GCTrackObj last_track_obj = m_track_objs[m_track_objs.size() - 1];
            int last_area = last_track_obj.rect.width * last_track_obj.rect.height;
            int cur_area = trackObj.rect.width * trackObj.rect.height;
            if (cur_area >= last_area * 4) {
                if (m_drop_reason == DropReason::DR_INIT || m_drop_reason == DropReason::TOO_BIG) {
                    m_drop_reason = DropReason::TOO_BIG;
                    m_drop_track_obj_coutinuous_count ++;
                } else {
                    m_drop_reason = DropReason::DR_INIT;
                    m_drop_track_obj_coutinuous_count = 0;
                }
                if (m_drop_track_obj_coutinuous_count > gc_app_config.base_config.alg_config.subscribe_yuv_fps * max_continuous_drop_frame_time) {
                    m_dirty = true;
                }
                LOG_INFO("camera_key=%s, track_id=%d ignore the too big area obj compare to last frame, cur_area=%d, last_area=%d, m_drop_track_obj_coutinuous_count=%d", m_camera_key.c_str(), m_track_id, cur_area, last_area, m_drop_track_obj_coutinuous_count);
                return false;
            }
            if (cur_area <= last_area / 4) {
                if (m_drop_reason == DropReason::DR_INIT || m_drop_reason == DropReason::TOO_SMALL) {
                    m_drop_reason = DropReason::TOO_SMALL;
                    m_drop_track_obj_coutinuous_count ++;
                } else {
                    m_drop_reason = DropReason::DR_INIT;
                    m_drop_track_obj_coutinuous_count = 0;
                }
                if (m_drop_track_obj_coutinuous_count > gc_app_config.base_config.alg_config.subscribe_yuv_fps * max_continuous_drop_frame_time) {
                    m_dirty = true;
                }
                LOG_INFO("camera_key=%s, track_id=%d ignore the too small area obj compare to last frame, cur_area=%d, last_area=%d, m_drop_track_obj_coutinuous_count=%d", m_camera_key.c_str(), m_track_id, cur_area, last_area, m_drop_track_obj_coutinuous_count);
                return false;
            }
            /*
                如果框变小，面积或高度小于上一帧的1/M or 1/N，并且IoU（U为新框）大于0.9，则认为是检测框漂移变化导致。
                如果框变大，面积或高度大于上一帧的M or N倍，并且IoU（U为旧框）大于0.9，则认为是检测框漂移变化导致。
            */
            if (cur_area <= last_area / 3 || trackObj.rect.height <= last_track_obj.rect.height / 2) {
                float iou_v = iou(last_track_obj.tlbr, trackObj.tlbr, 1);
                if (iou_v > 0.9) {
                    if (m_drop_reason == DropReason::DR_INIT || m_drop_reason == DropReason::TOO_SMALL_WITH_IOU) {
                        m_drop_reason = DropReason::TOO_SMALL_WITH_IOU;
                        m_drop_track_obj_coutinuous_count ++;
                    } else {
                        m_drop_reason = DropReason::DR_INIT;
                        m_drop_track_obj_coutinuous_count = 0;
                    }
                    if (m_drop_track_obj_coutinuous_count > gc_app_config.base_config.alg_config.subscribe_yuv_fps * max_continuous_drop_frame_time) {
                        m_dirty = true;
                    }
                    LOG_INFO("camera_key=%s, track_id=%d ignore the too small and too near by last frame obj, cur_area=%d, last_area=%d, iou=%.2f, m_drop_track_obj_coutinuous_count=%d", m_camera_key.c_str(), m_track_id, cur_area, last_area, iou_v, m_drop_track_obj_coutinuous_count);
                    return false;
                }
            }
            if (cur_area >= last_area * 3 || trackObj.rect.height >= last_track_obj.rect.height * 2) {
                float iou_v = iou(last_track_obj.tlbr, trackObj.tlbr, 0);
                if (iou_v > 0.9) {
                    if (m_drop_reason == DropReason::DR_INIT || m_drop_reason == DropReason::TOO_BIG_WITH_IOU) {
                        m_drop_reason = DropReason::TOO_BIG_WITH_IOU;
                        m_drop_track_obj_coutinuous_count ++;
                    } else {
                        m_drop_reason = DropReason::DR_INIT;
                        m_drop_track_obj_coutinuous_count = 0;
                    }
                    if (m_drop_track_obj_coutinuous_count > gc_app_config.base_config.alg_config.subscribe_yuv_fps * max_continuous_drop_frame_time) {
                        m_dirty = true;
                    }
                    LOG_INFO("camera_key=%s, track_id=%d ignore the too big and too near by last frame obj, cur_area=%d, last_area=%d, iou=%.2f, m_drop_track_obj_coutinuous_count=%d", m_camera_key.c_str(), m_track_id, cur_area, last_area, iou_v, m_drop_track_obj_coutinuous_count);
                    return false;
                }
            }
        }
        if (m_track_objs.size() >= 1) {
            vector<GCTrackObj>::iterator iter_cross = std::find_if(m_track_objs.begin(), m_track_objs.end(), [](GCTrackObj track_obj) {
                return track_obj.is_cross_frame; 
            });
            int pos_side = IsPointInPolygon(trackObj.cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1;
            int last_pos_side = IsPointInPolygon(m_track_objs[m_track_objs.size() - 1].cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1;
            if (m_track_result.inout != InoutType::INIT && pos_side == m_start_pos_side) {
                m_track_result.inout = InoutType::INIT;
                LOG_INFO("camera_key=%s, track_id=%d reset track result init by start pos and end pos being the sameside of the roi", m_camera_key.c_str(), m_track_id);
            }
            // 取最后一次跨线后的帧为跨线帧
            if (pos_side != m_start_pos_side && pos_side != last_pos_side) {
                if (iter_cross != m_track_objs.end()) {
                    iter_cross->is_cross_frame = false;
                }
                trackObj.is_cross_frame = true;

                m_track_result.inout = m_start_pos_side == 0 ? InoutType::IN : InoutType::OUT;
            }
        }
        if (trackObj.is_cross_frame) {
            LOG_INFO("camera_key=%s, track_id=%d cross frame idx=%d", m_camera_key.c_str(), m_track_id, m_track_objs.size());
        }
        m_track_objs.emplace_back(trackObj);

        // LOG_DEBUG("camera_key=%s, track_id=%d size of m_track_objs=%d", m_camera_key.c_str(), m_track_id, m_track_objs.size());

        return true;
    }

    void InoutTrack::update(bool hasNewTrackObj)
    {
        // LOG_DEBUG("camera_key=%s, track_id=%d update, size of m_track_objs=%d, hasNewTrackObj=%d, m_track_end=%d, m_dirty=%d", m_camera_key.c_str(), m_track_id, m_track_objs.size(), hasNewTrackObj, m_track_end, m_dirty);
        
        if (m_track_end || m_dirty) {
            return;
        }
        std::time_t now = std::time(nullptr);
        std::time_t last_obj_past_time = now - m_last_track_obj_time;
        if (m_track_objs.size() == 1) {
            // LOG_DEBUG("camera_key=%s, track_id=%d track end by too less objs", m_camera_key.c_str(), m_track_id);
            if (last_obj_past_time > gc_app_config.base_config.alg_config.track_max_idle_time) {
                m_dirty = true;
                LOG_INFO("camera_key=%s, track_id=%d track end, size of m_track_objs=%d, inout=%d, m_dirty=%d by last_obj_past_time=%d s", m_camera_key.c_str(), m_track_id, m_track_objs.size(), m_track_result.inout, m_dirty, last_obj_past_time);
            }
            return;
        }
        if (hasNewTrackObj) {
            if (m_track_result.inout == InoutType::INIT && m_track_objs.size() > gc_app_config.base_config.alg_config.track_max_idle_obj) {
                m_track_objs.erase(m_track_objs.begin(), m_track_objs.begin() + 1);
            } else if (m_track_objs.size() > std::min(gc_app_config.base_config.alg_config.track_max_obj, gc_app_config.base_config.alg_config.img_cache_size)) {
                m_track_objs.erase(m_track_objs.begin(), m_track_objs.begin() + 1);
            }
        }

        if (last_obj_past_time > gc_app_config.base_config.alg_config.track_max_idle_time) {
            m_track_end = true;
            if (m_track_result.inout == InoutType::INIT) {
                m_dirty = true;
                LOG_INFO("camera_key=%s, track_id=%d set dirty true cause track result is init after last_obj_past_time", m_camera_key.c_str(), m_track_id);
            } else {
                checkInoutDistance();
                if (m_track_end) {
                    // 正常入场后目标应该始终存在，如果走到这里说明置信度不高
                    if (m_track_result.inout == InoutType::IN) {
                        m_track_result.inout_conf = 90.f;
                    }
                    // 正常出场前追踪目标始终存在, 连续追踪帧数过少说明置信度不高
                    else if (m_track_objs.size() <= 5) {
                        m_track_result.inout_conf = 90.f;
                    } else {
                        m_track_result.inout_conf = 95.f;
                    }

                    LOG_INFO("camera_key=%s, track_id=%d track end, size of m_track_objs=%d, inout=%d, m_track_end=%d by last_obj_past_time=%d s", m_camera_key.c_str(), m_track_id, m_track_objs.size(), m_track_result.inout, m_track_end, last_obj_past_time);
                } else {
                    m_track_result.inout = InoutType::INIT;
                    m_dirty = true;

                    LOG_INFO("camera_key=%s, track_id=%d track result reinit after checkInoutDistance", m_camera_key.c_str(), m_track_id);
                }
            }
            return;
        }
        //  轨迹没有新的目标时只计算时间，不重复判断其他逻辑
        if (!hasNewTrackObj) {
            return;
        }
        // TODO 增加对相机照射方向、车位方向相关的判断逻辑

        // 3. 判断首末点位置关系是否有变化
        // int end_pos_side = IsPointInPolygon(m_track_objs[m_track_objs.size() - 1].cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1;
        // if (m_start_pos_side == end_pos_side) {
        //     if (m_track_result.inout != InoutType::INIT) {
        //         m_track_result.inout = InoutType::INIT; // 恢复无出入场行为初始状态
        //         LOG_INFO("camera_key=%s, track_id=%d track reinit cause by start pos and end pos are all outside or inside the roi", m_camera_key.c_str(), m_track_id);
        //         return;
        //     }
        // }

        // m_track_result.inout = m_start_pos_side == 0 ? InoutType::IN : InoutType::OUT;

        int frame_count_after_cross_line = 0;
        for (int i = m_track_objs.size() - 1; i > 0; i--) {
            if (m_track_objs[i].is_cross_frame) {
                break;
            }
            frame_count_after_cross_line ++;
        }

        // 5.0 入场目标连续N秒未移动，则认为最终确定（这里暂且忽略掉出场后车辆未移动出相机视线范围的情况） // TODO configable
        int stable_time_for_end = 10;
        // if (gc_app_config.base_config.alg_config.subscribe_yuv_fps == 1) {
        //     stable_time_for_end = 5;
        // }
        if (m_track_result.inout == InoutType::IN && frame_count_after_cross_line > gc_app_config.base_config.alg_config.subscribe_yuv_fps * stable_time_for_end) {
            bool is_moving = false;
            float max_dis = 0.f;
            int i = m_track_objs.size() - gc_app_config.base_config.alg_config.subscribe_yuv_fps * stable_time_for_end;
            for (int j = i+1; j < m_track_objs.size(); j++) {
                float dis = distance(m_track_objs[i].cxby, m_track_objs[j].cxby);
                if (dis > max_dis) {
                    max_dis = dis;
                }
                if (max_dis > 15) { // TODO configable
                    is_moving = true;
                    break;
                }
            }
            if (is_moving) {
                LOG_INFO("camera_key=%s, track_id=%d is moving, max_dis=%.2f", m_camera_key.c_str(), m_track_id, max_dis);
                return;
            } else {
                m_track_end = true;
                LOG_INFO("camera_key=%s, track_id=%d track end by not moving, max_dis=%.2f", m_camera_key.c_str(), m_track_id, max_dis);
            }
        }
        // 5.1 判断移动距离, 入场：增加最后两帧/首尾帧移动距离判断
        if (m_track_end) {
            checkInoutDistance();
            if (m_track_end) {
                m_track_result.inout_conf = 95.f;
                
                LOG_INFO("camera_key=%s, track_id=%d track end, size of m_track_objs=%d, inout=%d, m_start_time=%d, m_track_end=%d", m_camera_key.c_str(), m_track_id, m_track_objs.size(), m_track_result.inout, m_start_time, m_track_end);
            }
        }


        return;
    }

    inline void InoutTrack::checkInoutDistance()
    {
        bool top_in_view = m_track_objs[m_track_objs.size() - 1].tlbr[1] > 10; // 检测框上边缘是否在视角内
        if (gc_app_config.camera_config_map[m_camera_key].style != GC_CAMERA_STYLE::STYLE_BAN && m_track_result.inout == InoutType::IN) {
            int distance_tlbr = top_in_view ? distance(m_track_objs[m_track_objs.size() - 2].tlbr, m_track_objs[m_track_objs.size() - 1].tlbr) : 0;
            int distance_trbl = top_in_view ? distance(m_track_objs[m_track_objs.size() - 2].trbl, m_track_objs[m_track_objs.size() - 1].trbl) : 0;
            int distance_cxby = distance(m_track_objs[m_track_objs.size() - 2].cxby, m_track_objs[m_track_objs.size() - 1].cxby);
            bool last_2_frame_moved = true;
            last_2_frame_moved = top_in_view ? (distance_tlbr > 20 && distance_trbl > 20) : distance_cxby > 20; // TODO configable

            m_track_end = !last_2_frame_moved;

            LOG_INFO("camera_key=%s, track_id=%d top_in_view=%d, distance_tlbr=%d, distance_trbl=%d, distance_cxby=%d, last_2_frame_moved=%d, m_track_end=%d", m_camera_key.c_str(), m_track_id, top_in_view, distance_tlbr, distance_trbl, distance_cxby, last_2_frame_moved, m_track_end);
        }
        if (m_track_end) {
            // int first_last_distance_x = std::abs(m_1st_track_obj.tlbr[0] - m_track_objs[m_track_objs.size() - 1].tlbr[0]);
            // int first_last_distance_y = std::abs(m_1st_track_obj.tlbr[1] - m_track_objs[m_track_objs.size() - 1].tlbr[1]);
            int distance_tlbr = top_in_view ? distance(m_1st_track_obj.tlbr, m_track_objs[m_track_objs.size() - 1].tlbr) : 0;
            int distance_trbl = top_in_view ? distance(m_1st_track_obj.trbl, m_track_objs[m_track_objs.size() - 1].trbl) : 0;
            int distance_cxby = distance(m_1st_track_obj.cxby, m_track_objs[m_track_objs.size() - 1].cxby);
            // TODO 根据所在位置车辆宽度计算距离阈值     
            // bool first_last_moved = distance_tlbr > 50 && distance_trbl > 50; // TODO configable
            // if (gc_app_config.camera_config_map[m_camera_key].style != GC_CAMERA_STYLE::STYLE_BAN && first_last_moved) {
            //     first_last_moved = first_last_distance_x > (gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_XIE ? 50 : 50); // TODO configable
            //     first_last_moved = first_last_moved && first_last_distance_y > (gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_XIE ? 50 : 50); // TODO configable
            // }
            // m_track_end = first_last_moved;
            // LOG_INFO("camera_key=%s, track_id=%d distance_tlbr=%d, distance_trbl=%d, first_last_distance_x=%d, first_last_distance_y=%d, first_last_moved=%d, m_track_end=%d ", m_camera_key.c_str(), m_track_id, distance_tlbr, distance_trbl, first_last_distance_x, first_last_distance_y, first_last_moved, m_track_end);

            bool first_last_moved = top_in_view ? (distance_tlbr > 100 && distance_trbl > 100) : distance_cxby > 50; // TODO configable
            m_track_end = first_last_moved;

            LOG_INFO("camera_key=%s, track_id=%d top_in_view=%d, distance_tlbr=%d, distance_trbl=%d, distance_cxby=%d, first_last_moved=%d, m_track_end=%d", m_camera_key.c_str(), m_track_id, top_in_view, distance_tlbr, distance_trbl, distance_cxby, first_last_moved, m_track_end);
            
            // if (m_track_end) {
            //     m_start_pos_side == -1;
            // }
        }
    }

    void InoutTrack::End()
    {
        m_track_end = true;
        if (gc_app_config.camera_config_map[m_camera_key].style != GC_CAMERA_STYLE::STYLE_BAN) {
            // 入场行为，如果首帧目标与泊位框IOU(U为目标框非Union) > 0.8则过滤掉
            if (m_track_result.inout == InoutType::IN) {
                float first_iou = iou_poly(gc_app_config.camera_config_map[m_camera_key].roi, m_track_objs[0].polygon, 0);
                if (first_iou > 0.85) { // TODO configable
                    m_track_result.inout = InoutType::INIT;

                    LOG_WARN("track_id=%d reinit by first track obj and roi iou too big for in, first_iou=%.3f", m_track_id, first_iou);
                    return;
                }
            }
            // 出场行为，如果尾帧目标与泊位框IOU(U为目标框非Union) > 0.8则过滤掉
            if (m_track_result.inout == InoutType::OUT) {
                float last_iou = iou_poly( gc_app_config.camera_config_map[m_camera_key].roi, m_track_objs[m_track_objs.size() - 1].polygon, 0);
                if (last_iou > 0.85) { // TODO configable
                    m_track_result.inout = InoutType::INIT;

                    LOG_WARN("track_id=%d reinit by last track obj and roi iou too big for out, last_iou=%.3f", m_track_id, last_iou);
                    return;
                }
            }
            // TODO 考虑如何过滤入场前已存在的车牌
        }
        getPlate();
        // 获取车牌会同步验证车牌轨迹，某些条件下会重置出入场行为初始状态，详见getPlate函数内注释
        if (m_track_result.inout == InoutType::INIT) {
            LOG_WARN("track_id=%d reinit after get plate", m_track_id);
            return;
        }
        // TODO 过滤掉入场目标车牌在目标出现前的帧存在的行为

        // 如果入场前该车牌出入场记录为入场，则重置行为（改为平台侧处理）
        // if (m_track_result.inout == InoutType::IN && !m_track_result.plate.empty()) {
        //     std::tm t = {};
        //     std::istringstream ss(m_track_objs[m_track_objs.size() - 1].time_str);
        //     ss >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
        //     std::time_t last_track_frame_ts = mktime(&t);
        //     try {
        //         char sql[1024];
        //         sprintf(sql, "SELECT inout_type, time_str FROM inout where plate='%s' order by id desc limit 1", m_track_result.plate.c_str());
        //         SQLite::Statement query(gc_db, sql);
        //         while (query.executeStep()) {
        //             int inout = query.getColumn(0).getInt();
        //             std::string time_str = query.getColumn(1).getString();
        //             t = {};
        //             std::istringstream sss(time_str);
        //             sss >> std::get_time(&t, "%Y-%m-%d %H:%M:%S");
        //             std::time_t current_frame_ts = mktime(&t);
        //             if (current_frame_ts - last_track_frame_ts > 24 * 60 * 60) { // TODO configable
        //                 continue;
        //             }
        //             if (inout == InoutType::IN) {
        //                 m_track_result.inout = InoutType::INIT;

        //                 LOG_INFO("camera_key=%s, track_id=%d reinit by last same plate is also in type, plate=%s, time_str=%s", m_camera_key.c_str(), m_track_id, m_track_result.plate.c_str(), time_str.c_str());
        //                 break;
        //             }
        //         }
        //     } catch (std::exception &e) {
        //         LOG_ERROR("track_id=%d check same plate in record SQLite exception: %s", m_track_id, e.what());
        //     }
        // }
        // 因为出场行为的判断逻辑是目标消失后才会确认，这里判断如果车牌在出场后的帧中存在，则重置行为
        // if (m_track_result.inout == InoutType::OUT && !m_track_result.plate.empty()) {
        //     GCTrackData track_data;
        //     {
        //         std::lock_guard<std::mutex> l(last_track_data_mutex);
        //         last_track_data.cloneExcludeYuvForwardBuf(track_data);
        //     }

        //     LOG_INFO("camera_key=%s, track_id=%d last_track_data=%s, size of objBoxs=%d, 1st class=%d", m_camera_key.c_str(), m_track_id, track_data.img_path, track_data.objBoxs.size(), track_data.objBoxs.size() > 0 ? track_data.objBoxs[0].enClass : -1);

        //     cv::Mat frame_img;
        //     GCImageCache::GetInstance().Get(track_data.img_path, frame_img);
        //     if (frame_img.empty()) {
        //         LOG_ERROR("track_id=%d last_track_data image is empty for path=%s", m_track_id, track_data.img_path);
        //     } else {
        //         try {
        //             bool use_local_lpr = gc_app_config.base_config.lpr_host.rfind("local", 0) == 0;
        //             for (auto &box: track_data.objBoxs) {
        //                 if (std::find(gc_app_config.base_config.alg_config.plate_classes.begin(), gc_app_config.base_config.alg_config.plate_classes.end(), box.enClass) == gc_app_config.base_config.alg_config.plate_classes.end()) {
        //                     continue;
        //                 }
        //                 GCRect rect = GCRect(box.rect.x / m_mapRatio, box.rect.y / m_mapRatio, box.rect.width / m_mapRatio, box.rect.height / m_mapRatio,
        //                     LandmarkInfo{
        //                         box.landmark.x0 / m_mapRatio, box.landmark.y0 / m_mapRatio,
        //                         box.landmark.x1 / m_mapRatio, box.landmark.y1 / m_mapRatio,
        //                         box.landmark.x2 / m_mapRatio, box.landmark.y2 / m_mapRatio,
        //                         box.landmark.x3 / m_mapRatio, box.landmark.y3 / m_mapRatio
        //                     },
        //                     box.fScore,
        //                     box.enClass
        //                 );
        //                 int x = std::max(rect.x, 0);
        //                 int y = std::max(rect.y, 0);
        //                 int w = rect.x < 0 ? rect.width + rect.x : rect.width;
        //                 int h = rect.y < 0 ? rect.height + rect.y : rect.height;

        //                 cv::Mat plate_img = frame_img(cv::Rect(x, y, std::min(w, frame_img.cols - x), std::min(h, frame_img.rows - y)));
        //                 std::vector<cv::Point2f> src_pts = {
        //                     cv::Point2f(rect.landmark.x0 - rect.x, rect.landmark.y0 - rect.y),
        //                     cv::Point2f(rect.landmark.x1 - rect.x, rect.landmark.y1 - rect.y),
        //                     cv::Point2f(rect.landmark.x2 - rect.x, rect.landmark.y2 - rect.y),
        //                     cv::Point2f(rect.landmark.x3 - rect.x, rect.landmark.y3 - rect.y)
        //                 };
        //                 std::vector<cv::Point2f> dst_pts = {
        //                     cv::Point2f(0, 0),
        //                     cv::Point2f(gc_app_config.base_config.alg_config.lpr_img_w, 0),
        //                     cv::Point2f(gc_app_config.base_config.alg_config.lpr_img_w, gc_app_config.base_config.alg_config.lpr_img_h),
        //                     cv::Point2f(0, gc_app_config.base_config.alg_config.lpr_img_h)
        //                 };
        //                 cv::Mat m = cv::getPerspectiveTransform(src_pts, dst_pts);
        //                 cv::Mat perspective_mat;
        //                 cv::warpPerspective(plate_img, perspective_mat, m, cv::Size(gc_app_config.base_config.alg_config.lpr_img_w, gc_app_config.base_config.alg_config.lpr_img_h));

        //                 nlohmann::json lpr_result = use_local_lpr ? GCLpr::GetInstance().lpr_local(perspective_mat, static_cast<GC_PLATE_TYPE>(rect.enClass | GC_PLATE_TYPE::SINGLE)) : GCLpr::GetInstance().lpr_remote(perspective_mat);
        //                 if (lpr_result.empty() || lpr_result["errcode"].get<int>() != 0) {
        //                     continue;
        //                 }
        //                 nlohmann::json plate_data_list = lpr_result["data"];
        //                 if (plate_data_list.empty()) {
        //                     continue;
        //                 }
        //                 for (auto& plate_data : plate_data_list) {
        //                     std::string plate = plate_data["plate"];
        //                     if (plate.empty()) {
        //                         continue;
        //                     }
        //                     if (plate.compare(m_track_result.plate.c_str()) == 0) {

        //                         LOG_WARN("track_id=%d reinit when plate=%s still exist in the newer frame after inout=%d", m_track_id, plate.c_str(), m_track_result.inout);

        //                         m_track_result.inout = InoutType::INIT;
        //                         return;
        //                     }
        //                 }
        //             }
        //         } catch (std::exception& e) {
        //             LOG_ERROR("track_id=%d check plate exist or not after inout type out: %s", m_track_id, e.what());
        //         }
        //     }
        // }
        getInoutTime();
        struct timespec time1 = {0, 0};
        clock_gettime(CLOCK_BOOTTIME, &time1);
        m_track_result.id = md5(gc_app_config.camera_config_map[m_camera_key].sn + to_string(time1.tv_sec * 1000 + time1.tv_nsec / 1000000));

        getProofImgs();
        if (m_track_result.proof_track_objs.size() < 2) {
            LOG_ERROR("track_id=%d inout=%d track end with track proof obj size=%d", m_track_id, m_track_result.inout, m_track_result.proof_track_objs.size());
        }
        // if (gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_BAN) {
            getStateImgs();
        // }
        if (gc_app_config.camera_config_map[m_camera_key].style != GC_CAMERA_STYLE::STYLE_BAN) {
            // 如果非半封闭出场有车牌，但是和最近N次入场都不匹配的，纠正为模糊匹配上的入场车牌，如果未匹配则重置该行为（N=相机覆盖泊位数）
            if (m_track_result.inout == InoutType::OUT && !m_track_result.plate.empty()) {
                try {
                    std::vector<std::string> latest_in_plates;
                    std::vector<std::string> latest_in_plate_colors;
                    bool match = false;
                    // 1. 先判断最近一次相同车牌行为是否为入场
                    char sql[1024];
                    sprintf(sql, "SELECT plate_color, inout_type FROM inout where plate='%s' order by id desc limit 1", m_track_result.plate.c_str());
                    SQLite::Statement   query(gc_db, sql);
                    while (query.executeStep())
                    {
                        std::string latest_plate_color = query.getColumn(0).getString();
                        int inout = query.getColumn(1).getInt();
                        if (inout == InoutType::IN) {
                            match = true;
                            m_track_result.inout_conf = 99.f;
                            if (latest_plate_color != m_track_result.plate_color) {
                                m_track_result.plate_color = latest_plate_color;

                                LOG_INFO("camera_key=%s, track_id=%d reset plate color by last same plate in type, plate=%s, plate_color=%s", m_camera_key.c_str(), m_track_id, m_track_result.plate.c_str(), m_track_result.plate_color.c_str());
                            }
                            break;
                        }
                    }
                    if (!match) {

                        LOG_INFO("camera_key=%s, track_id=%d no match in type record for plate=%s, try to find a similar one to match", m_camera_key.c_str(), m_track_id, m_track_result.plate.c_str());

                        // 2. 如果没有车牌一致的入场，则从最近N次出入场记录内查找无匹配出场且车牌相似度较大的入场作为匹配，修订出场车牌
                        std::string result_plate_without_province = m_track_result.plate.substr(3);
                        std::vector<std::string> latest_out_plates;
                        sprintf(sql, "SELECT plate, plate_color, inout_type FROM inout where plate is not null and plate != '' order by id desc limit 20"); // TODO configable
                        SQLite::Statement   query0(gc_db, sql);
                        while (query0.executeStep())
                        {
                            std::string latest_plate = query0.getColumn(0).getString().substr(3); // 注：汉字utf8占用3字节
                            std::string latest_plate_color = query0.getColumn(1).getString();
                            int inout = query0.getColumn(2).getInt();
                            if (inout == InoutType::OUT) {
                                latest_out_plates.emplace_back(latest_plate);
                                continue;
                            }
                            std::string latest_plate_without_province = latest_plate.substr(3);
                            if (std::find(latest_out_plates.begin(), latest_out_plates.end(), latest_plate) == latest_out_plates.end()) {
                                double similarity = levenshtein::GetInstance().wsimilarity(std::wstring(latest_plate_without_province.begin(), latest_plate_without_province.end()), std::wstring(result_plate_without_province.begin(), result_plate_without_province.end()));
                                if (similarity > 0.85) { // TODO configable 
                                    LOG_INFO("camera_key=%s, track_id=%d reset plate by the latest wild match in type, plate=%s, fixed_plate=%s, similarity=%.2f", m_camera_key.c_str(), m_track_id, m_track_result.plate.c_str(), latest_plate.c_str(), similarity);

                                    match = true;
                                    m_track_result.inout_conf = 91.f;
                                    m_track_result.plate = latest_plate;
                                    m_track_result.plate_conf = 61.f;
                                    m_track_result.plate_color = latest_plate_color;
                                }
                            }
                        }
                    }
                    if (!match) {
                        // m_track_result.inout = InoutType::INIT;

                        // LOG_WARN("track_id=%d reinit cause no match plate found for latest in type record, plate=%s", m_track_id, m_track_result.plate.c_str());
                        LOG_WARN("track_id=%d no match plate found for latest in type record, plate=%s", m_track_id, m_track_result.plate.c_str());
                    }
                }
                catch (std::exception& e)
                {
                    LOG_ERROR("track_id=%d check plate SQLite exception: %s", m_track_id, e.what());
                }
            }
            // 非调试模式如果明显错误车牌则不上报数据
            if (!gc_app_config.camera_config_map[m_camera_key].debug) {
                if (m_track_result.plate.empty()) {
                    m_track_result.inout == InoutType::INIT;

                    LOG_WARN("track_id=%d reinit by plate is empty", m_track_id);
                    return;
                }
                if (m_track_result.plate.size() < 3 + 6) {
                    m_track_result.inout == InoutType::INIT;

                    LOG_WARN("track_id=%d reinit by plate is too short", m_track_id);
                    return;
                }
                if (m_track_result.plate.size() > 3 + 5 + 3) {
                    m_track_result.inout == InoutType::INIT;

                    LOG_WARN("track_id=%d reinit by plate is too long", m_track_id);
                    return;
                }
            }            
            getBerth();
            // 如果非半封闭出场有车牌无泊位号，尝试从入场记录寻找泊位号
            // if (m_track_result.inout == InoutType::OUT && m_track_result.berth.empty() && !m_track_result.plate.empty()) {
            //     // gc_db.exec("CREATE TABLE inout (id INTEGER PRIMARY KEY, inout_id TEXT, plate TEXT, time_str TEXT, berth TEXT, inout_type INTEGER, plate_conf INTEGER, inout_conf INTEGER, berth_conf INTEGER, plate_color TEXT)");
            //     try {
            //         char sql[1024];
            //         sprintf(sql, "SELECT berth, inout_type, plate_conf, inout_conf, berth_conf FROM inout where plate='%s' order by id desc limit 1", m_track_result.plate.c_str());
            //         SQLite::Statement   query(gc_db, sql);
            //         while (query.executeStep())
            //         {
            //             if (query.getColumn(1).getInt() == InoutType::OUT) {
            //                 break;
            //             }
            //             if (query.getColumn(2).getInt() < 80) {
            //                 break;
            //             }
            //             if (query.getColumn(3).getInt() < 80) {
            //                 break;
            //             }
            //             if (query.getColumn(4).getInt() < 80) {
            //                 break;
            //             }
            //             m_track_result.berth = query.getColumn(0).getString();
            //             m_track_result.berth_conf = 60.f;

            //             LOG_INFO("camera_key=%s, track_id=%d set berth by last inout from the same plate, berth=%s", m_camera_key.c_str(), m_track_id, m_track_result.berth.c_str());
            //         }
            //     }
            //     catch (std::exception& e)
            //     {
            //         LOG_ERROR("track_id=%d find berth SQLite exception: %s", m_track_id, e.what());
            //     }
            // }
            // 如果非半封闭出场根据入场记录二次确认或更新泊位号（因为只有泊位号相同才能匹配成停车记录，所以以入场泊位号为准）
            if (m_track_result.inout == InoutType::OUT && !m_track_result.plate.empty()) {
                try {
                    char sql[1024];
                    sprintf(sql, "SELECT berth, inout_type, plate_conf, inout_conf, berth_conf FROM inout where plate='%s' order by id desc limit 1", m_track_result.plate.c_str());
                    SQLite::Statement   query(gc_db, sql);
                    while (query.executeStep())
                    {
                        if (query.getColumn(1).getInt() == InoutType::OUT) {
                            break;
                        }
                        if (m_track_result.berth == query.getColumn(0).getString()) {
                            m_track_result.berth_conf = 95.f;
                        } else {
                            m_track_result.berth = query.getColumn(0).getString();
                            m_track_result.berth_conf = 80.f;
                        }

                        LOG_INFO("camera_key=%s, track_id=%d set berth and berth conf by last in from the same plate, berth=%s", m_camera_key.c_str(), m_track_id, m_track_result.berth.c_str());
                    }
                }
                catch (std::exception& e)
                {
                    LOG_ERROR("track_id=%d find berth SQLite exception: %s", m_track_id, e.what());
                }
            }
            // 斜照/垂照：入场行为，如果跨线前两帧泊位上有车则过滤掉；出场行为，如果跨线后2帧/最后一帧泊位上有车则过滤掉
            if ((
                    gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_XIE
                    ||
                    gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_CHUI
                ) 
                && !m_track_result.berth.empty()) {
                vector<GCTrackObj>::iterator iter_cross = std::find_if(m_track_objs.begin(), m_track_objs.end(), [](GCTrackObj track_obj) {
                    return track_obj.is_cross_frame;
                });
                std::string berthName = m_track_result.berth;
                vector<GC_BERTH>::iterator iter_berth = std::find_if(gc_app_config.camera_config_map[m_camera_key].berthes.begin(), gc_app_config.camera_config_map[m_camera_key].berthes.end(), [berthName](GC_BERTH berth) {
                    return berth.name == berthName;
                });
                if (iter_berth != gc_app_config.camera_config_map[m_camera_key].berthes.end()) {
                    bool valid = true;
                    if (m_track_result.inout == InoutType::OUT && iter_cross != m_track_objs.end()) {
                        if (iter_cross + 2 != m_track_objs.end()) {
                            iter_cross += 2;
                        } else {
                            iter_cross = m_track_objs.end() - 1;
                        }
                    } else if (m_track_result.inout == InoutType::IN && iter_cross - 1 != m_track_objs.begin()) {
                        iter_cross -= 2;
                    } else {
                        valid = false;
                    }
                    if (valid) {
                        if (std::any_of(iter_cross->car_boxes.begin(), iter_cross->car_boxes.end(), [this, iter_berth](GCRect rect) {
                            std::vector<int> cxby;
                            switch (gc_app_config.camera_config_map[m_camera_key].pos)
                            {
                            case GC_CAMERA_POS::POS_LEFT:
                                cxby = { rect.x + rect.width * 2 / 3, rect.y + rect.height };
                                break;
                            case GC_CAMERA_POS::POS_RIGHT:
                                cxby = { rect.x + rect.width / 3, rect.y + rect.height };
                                break;
                            default:
                                cxby = { rect.x + rect.width / 2, rect.y + rect.height };
                                break;
                            }                            
                            return this->IsPointInPolygon(cxby, iter_berth->roi) != 0;
                        })) {
                            LOG_WARN("track_id=%d reinit by having car in berth after out or before in, plate=%s, berth=%s, inout=%d", m_track_id, m_track_result.plate.c_str(), m_track_result.berth.c_str(), m_track_result.inout);
                            m_track_result.inout = InoutType::INIT;
                            return;
                        }
                    }
                }
            }

            // 如果非半封闭出场无车牌且行为置信度较高，尝试从相同泊位上一次入场记录查找（行为置信度目前不太靠谱，先注释这里）
            // if (m_track_result.inout == InoutType::OUT && !m_track_result.berth.empty()) {
            //     // gc_db.exec("CREATE TABLE inout (id INTEGER PRIMARY KEY, inout_id TEXT, plate TEXT, time_str TEXT, berth TEXT, inout_type INTEGER, plate_conf INTEGER, inout_conf INTEGER, berth_conf INTEGER, plate_color TEXT)");
            //     if (m_track_result.plate.empty() && m_track_result.inout_conf >= 95) { // TODO configable
            //         try {
            //             char sql[1024];
            //             sprintf(sql, "SELECT plate, inout_type, plate_conf, inout_conf, berth_conf, plate_color FROM inout where berth='%s' order by id desc limit 1", m_track_result.berth.c_str());
            //             SQLite::Statement   query(gc_db, sql);
            //             while (query.executeStep())
            //             {
            //                 if (query.getColumn(1).getInt() == InoutType::OUT) {
            //                     break;
            //                 }
            //                 if (query.getColumn(2).getInt() < 80) {
            //                     break;
            //                 }
            //                 if (query.getColumn(3).getInt() < 80) {
            //                     break;
            //                 }
            //                 if (query.getColumn(4).getInt() < 80) {
            //                     break;
            //                 }
            //                 m_track_result.plate = query.getColumn(0).getString();
            //                 m_track_result.plate_conf = 60.f;
            //                 m_track_result.plate_color = query.getColumn(5).getString();

            //                 LOG_INFO("camera_key=%s, track_id=%d set plate by last in type from the same berth, plate=%s", m_camera_key.c_str(), m_track_id, m_track_result.plate.c_str());
            //             }
            //         }
            //         catch (std::exception& e)
            //         {
            //             LOG_ERROR("track_id=%d fix empty plate SQLite exception: %s", m_track_id, e.what());
            //         }
            //     }
            // }

            // 如果非半封闭入场，判断上一次该泊位行为是否为出场，如果是则确认行为；如果否，再判断x分钟前该泊位上是否有本次入场车牌号，已有则重置本次入场行为
            // 实现思路：1. 修改存状态图逻辑，本地1-2分钟存一次状态图，保留最新2张；是否上报状态图及上报周期根据工具配置；每次保存的状态图做车牌识别并存储和泊位对应关系。
            

        }
        saveDB();
        drawDebugInfo();
        upload();
        
        LOG_INFO("camera_key=%s, track_id=%d Track Result: id=%s, plate=%s[%s], inout=%d, inout_conf=%f, plate_conf=%f, pts=%lu, berth=%s, time_str=%s, proof_imgs_size=%d, state_imgs_size=%d", 
            m_camera_key.c_str(), m_track_id, m_track_result.id.c_str(), m_track_result.plate.c_str(), m_track_result.plate_color.c_str(), m_track_result.inout, m_track_result.inout_conf, m_track_result.plate_conf, m_track_result.pts, m_track_result.berth.c_str(), m_track_result.time_str, m_track_result.proof_track_objs.size(), m_track_result.state_track_objs.size());
    }

    void InoutTrack::upload() {
        //----- push to middleware begin -----//
        if (m_track_result.inout != InoutType::INIT && (gc_app_config.camera_config_map[m_camera_key].state == GC_CAMERA_STATE::STATE_DEBUG || gc_app_config.camera_config_map[m_camera_key].state == GC_CAMERA_STATE::STATE_ONLINE)) {
            Middleware::GetInstance().Upload_inout_data(m_track_result);
        }
        //----- push to middleware end -----//
    }

    void InoutTrack::saveDB() {
        try {
            // gc_db.exec("CREATE TABLE inout (id INTEGER PRIMARY KEY, inout_id TEXT, plate TEXT, time_str TEXT, berth TEXT, inout_type INTEGER, plate_conf INTEGER, inout_conf INTEGER, berth_conf INTEGER, plate_color TEXT)");    
            char sql[1024];
            sprintf(sql, "INSERT INTO inout VALUES (NULL, \"%s\", \"%s\", \"%s\", \"%s\", %d, %d, %d, %d, \"%s\")", m_track_result.id.c_str(), m_track_result.plate.c_str(), m_track_result.time_str, m_track_result.berth.c_str(), 
            m_track_result.inout, (int) m_track_result.plate_conf, (int) m_track_result.inout_conf, (int) m_track_result.berth_conf, m_track_result.plate_color.c_str());
            int nb = gc_db.exec(sql);

            LOG_INFO("camera_key=%s, track_id=%d inout_id=%s save db nb=%d", m_camera_key.c_str(), m_track_id, m_track_result.id.c_str(), nb);
        }
        catch (std::exception& e)
        {
            LOG_ERROR("track_id=%d SQLite exception: %s", m_track_id, e.what());
        }
    }   

    void InoutTrack::getBerth() {
        if (gc_app_config.camera_config_map[m_camera_key].berthes.empty()) {
            return;
        }
        // 如果相机只覆盖一个泊位，直接返回该泊位
        if (gc_app_config.camera_config_map[m_camera_key].berthes.size() == 1) {
            m_track_result.berth = gc_app_config.camera_config_map[m_camera_key].berthes[0].name;
            m_track_result.berth_conf = 100.f;

            LOG_INFO("camera_key=%s, track_id=%d berth=%s, berth_conf=%f, camera cover one berth only", m_camera_key.c_str(), m_track_id, m_track_result.berth, m_track_result.berth_conf);

            return;
        }
        std::map<std::string, int> berth_count_map;
        GCTrackObj check_track_obj = m_track_objs[m_track_result.inout == InoutType::OUT ? 0 : m_track_objs.size() - 1];
        // 出场判断跨线前的所有目标；入场反之
        if (m_track_result.inout == InoutType::IN) {
            std::reverse(m_track_objs.begin(), m_track_objs.end());
        }
        std::string first_frame_berth_name;
        int obj_total_count = 0;
        for (auto &track_obj: m_track_objs) {
            if (m_track_result.inout == InoutType::OUT && track_obj.is_cross_frame) {
                break;
            }
            for (auto &berth: gc_app_config.camera_config_map[m_camera_key].berthes) {
                if (berth.roi.empty()) {
                    continue;
                }
                if (IsPointInPolygon(track_obj.cxby, berth.roi) != 0) {
                    if (obj_total_count == 0) {
                        first_frame_berth_name = berth.name;
                    }
                    if (berth_count_map.find(berth.name) == berth_count_map.end()) {
                        berth_count_map.insert({ berth.name, 1 });
                    } else {
                        berth_count_map.find(berth.name)->second += 1;
                    }
                    break;
                }
            }
            obj_total_count ++;
            if (m_track_result.inout == InoutType::IN && track_obj.is_cross_frame) {
                break;
            }
        }
        if (berth_count_map.size() > 0) {
            std::vector<std::pair<std::string, int>> berth_count_list(berth_count_map.begin(), berth_count_map.end());
            std::sort(berth_count_list.begin(), berth_count_list.end(), [](const std::pair<std::string, int> &a, const std::pair<std::string, int> &b) {
                return a.second > b.second;
            });
            m_track_result.berth = berth_count_list[0].first;
            m_track_result.berth_conf = 100.f * berth_count_list[0].second / obj_total_count;

            // 第一帧所在泊位优先（解决穿越泊位情况）
            if (first_frame_berth_name != m_track_result.berth) {
                m_track_result.berth = first_frame_berth_name;
                m_track_result.berth_conf = 90.f;
            }

            LOG_INFO("camera_key=%s, track_id=%d berth=%s, count=%d, berth_conf=%f", m_camera_key.c_str(), m_track_id, m_track_result.berth, berth_count_list[0].second, m_track_result.berth_conf);
        } else {
            LOG_WARN("track_id=%d no berth", m_track_id);
        }
        if (m_track_result.inout == InoutType::IN) {
            std::reverse(m_track_objs.begin(), m_track_objs.end());
        }
    }

    void InoutTrack::getStateImgs()
    {
        if (m_track_objs.size() > 0) {
            std::ifstream fs;
            fs.open(m_1st_track_obj.img_path);
            if (fs.bad()) {
                m_track_result.state_track_objs.emplace_back(m_track_objs[0]);
            } else {
                m_track_result.state_track_objs.emplace_back(m_1st_track_obj);
            }
            fs.close();
        }
        if (m_track_objs.size() > 1) {
            m_track_result.state_track_objs.emplace_back(m_track_objs[m_track_objs.size() - 1]);
        }
    }

    void InoutTrack::getProofImgs()
    {
        // 取证据图
        // 出场情况下，如果将m_track_objs逆序排列，则取证据帧规则同入场相同，统一按照入场规则取证据图，最后再逆序回来
        if (m_track_result.inout == InoutType::OUT) {
            std::reverse(m_track_objs.begin(), m_track_objs.end());
        }
        // 核心逻辑：入场取跨线前车牌置信度最高的一帧或跨线前一帧、跨线后车牌置信度最高的一帧或最后一帧；出场取跨线前车牌置信度最高的一帧或第一帧，跨线后车牌置信度最高的一帧或跨线后第一帧
        vector<GCTrackObj>::iterator iter_cross = std::find_if(m_track_objs.begin(), m_track_objs.end(), [](GCTrackObj track_obj) {
            return track_obj.is_cross_frame;
        });
        vector<GCTrackObj>::iterator iter_proof_before_cross = iter_cross;
        vector<GCTrackObj>::iterator iter_proof_after_cross = std::prev(m_track_objs.end());
        // 如果没有跨线帧，则直接取1/4帧和3/4帧（理论上不会有这种情况）
        if (iter_cross == m_track_objs.end()) {
            int first_idx = m_track_objs.size() / 4;
            int last_idx = m_track_objs.size() * 3 / 4;
            iter_proof_before_cross = m_track_objs.begin() + first_idx;
            iter_proof_after_cross = m_track_objs.begin() + last_idx;
        } else {
            vector<GCTrackObj>::iterator iter = iter_cross;
            if (iter_cross != m_track_objs.begin()) {
                iter_proof_before_cross = m_track_result.inout == InoutType::IN ? iter_cross - 1 : iter_cross + 1; // 因为跨线帧设定为跨线后的一帧，这里入场情况需要减1, 出场因为做了逆序排列，无需减1
                bool has_plate = false;
                int max_plate_conf = 0;
                // 首张证据图排除跨线帧
                if (m_track_result.inout == InoutType::IN && iter != m_track_objs.begin()) {
                    iter --;
                }
                // if (m_track_result.inout == InoutType::OUT && iter != m_track_objs.end()) {
                //     iter ++;
                // }
                std::ifstream fs;
                for(;iter != m_track_objs.begin(); iter--) {
                    if (iter->lpr_result.empty() || iter->lpr_result["errcode"].get<int>() != 0) {
                        continue;
                    }
                    nlohmann::json plate_data_list = iter->lpr_result["data"];
                    if (plate_data_list.empty()) {
                        continue;
                    }
                    fs.open(iter->img_path);
                    if (fs.bad()) {
                        fs.close();
                        continue;
                    }
                    fs.close();
                    for (auto& plate_data : plate_data_list) {
                        std::string plate = plate_data["plate"];
                        if (plate.compare(m_track_result.plate.c_str()) == 0) {
                            has_plate = true;
                            int cur_plate_conf = plate_data["conf"].get<int>();
                            if (cur_plate_conf > max_plate_conf) {
                                max_plate_conf = cur_plate_conf;
                                iter_proof_before_cross = iter;
                            }
                        }
                    }
                }
                if (!has_plate) {
                    if (gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_BAN) {
                        if (iter_proof_before_cross != m_track_objs.begin()) {
                            iter_proof_before_cross;
                        }
                    } else {
                        iter_proof_before_cross -= (iter_cross - m_track_objs.begin()) / 2;
                    }
                }
            }
            iter = std::prev(m_track_objs.end());
            if (iter != m_track_objs.begin()) {
                vector<GCTrackObj>::iterator iter_stop = iter_cross;
                // 将跨线帧包含进来
                // if (m_track_result.inout == InoutType::IN && iter != m_track_objs.begin()) {
                //     iter_stop = iter_cross -1;
                // }
                if (m_track_result.inout == InoutType::OUT && iter != m_track_objs.end()) {
                    iter_stop = iter_cross + 1;
                }                
                bool has_plate = false;
                int max_plate_conf = 0;
                std::ifstream fs;
                for(;iter != iter_stop; iter--) {
                    if (iter->lpr_result.empty() || iter->lpr_result["errcode"].get<int>() != 0) {
                        continue;
                    }
                    nlohmann::json plate_data_list = iter->lpr_result["data"];
                    if (plate_data_list.empty()) {
                        continue;
                    }
                    fs.open(iter->img_path);
                    if (fs.bad()) {
                        fs.close();
                        continue;
                    }
                    for (auto& plate_data : plate_data_list) {
                        std::string plate = plate_data["plate"];
                        if (plate.compare(m_track_result.plate.c_str()) == 0) {
                            has_plate = true;
                            int cur_plate_conf = plate_data["conf"].get<int>();
                            if (cur_plate_conf > max_plate_conf) {
                                max_plate_conf = cur_plate_conf;
                                iter_proof_after_cross = iter;
                            }
                        }
                    }
                }
                if (!has_plate) {
                    if (gc_app_config.camera_config_map[m_camera_key].style == GC_CAMERA_STYLE::STYLE_BAN) {
                        iter_proof_after_cross = m_track_result.inout == InoutType::IN ? iter_cross : iter_cross + 1;
                    } else {
                        iter_proof_after_cross -= (m_track_objs.end() - iter_cross) / 2;
                    }
                }
            }
        }

        // 如果是推送到智慧件，补充首帧、cross帧 - 首帧 / 2、尾帧 - cross帧 / 2、尾帧
        if (gc_app_config.push_to_algv) {
            m_track_result.proof_track_objs.emplace_back(m_track_objs[0]);
            vector<GCTrackObj>::iterator iter = m_track_objs.begin() + (iter_proof_before_cross - m_track_objs.begin()) / 2;
            m_track_result.proof_track_objs.emplace_back(*iter);
        }
        m_track_result.proof_track_objs.emplace_back(*iter_proof_before_cross);
        if (gc_app_config.push_to_algv) {
            m_track_result.proof_track_objs.emplace_back(*iter_cross);
        }
        m_track_result.proof_track_objs.emplace_back(*iter_proof_after_cross);
        if (gc_app_config.push_to_algv) {
            m_track_result.proof_track_objs.emplace_back(*(iter_proof_after_cross + (m_track_objs.end() - iter_proof_after_cross)/ 2));
            m_track_result.proof_track_objs.emplace_back(m_track_objs[m_track_objs.size() - 1]);
        }

        for (auto &track_obj : m_track_result.proof_track_objs) {
            track_obj.is_proof = true;
        }

        if (m_track_result.inout == InoutType::OUT) {
            std::reverse(m_track_result.proof_track_objs.begin(), m_track_result.proof_track_objs.end());
            // 逆序回来，以免影响后续使用
            std::reverse(m_track_objs.begin(), m_track_objs.end());
        }
    }

    void InoutTrack::getInoutTime()
    {
        // 跨线帧作为出入场时间
        auto cross_line_track_obj = std::find_if(m_track_objs.begin(), m_track_objs.end(), [](GCTrackObj track_obj) {
            return track_obj.is_cross_frame;
        });
        if (cross_line_track_obj == m_track_objs.end()) {
            // 找不到跨线帧取最后一帧时间
            m_track_result.pts = m_track_objs[m_track_objs.size() - 1].pts;
            strcpy(m_track_result.time_str, m_track_objs[m_track_objs.size() - 1].time_str);

            LOG_WARN("track_id=%d end with no cross line track obj found", m_track_id);
        } else {
            m_track_result.pts = cross_line_track_obj->pts;
            strcpy(m_track_result.time_str, cross_line_track_obj->time_str);
        }
    }

    void InoutTrack::getPlate()
    {
        // 0. 每一帧都存在车牌且未移动，则重置出入场行为为初始化状态
        // 取出所有车牌
        bool is_all_frame_with_plate_and_not_move = true;
        vector<int> first_plate_tlbr;
        std::map<std::string, vector<GCTrackPlate>> plate_map;
        for (auto &track_obj: m_track_objs) {
            if (track_obj.lpr_result.empty() || track_obj.lpr_result["errcode"] != 0) {
                is_all_frame_with_plate_and_not_move = false;
                continue;
            }
            nlohmann::json plate_data_list = track_obj.lpr_result["data"];
            if (plate_data_list.empty()) {
                is_all_frame_with_plate_and_not_move = false;
                continue;
            }
            for (auto& plate_data : plate_data_list) {
                std::string plate = plate_data["plate"];
                nlohmann::json box = plate_data["box"];
                int plate_lx = max(track_obj.rect.x, 0) + box.at(0).get<int>();
                int plate_ty = max(track_obj.rect.y, 0) + box.at(1).get<int>();
                int plate_rx = max(track_obj.rect.x, 0) + box.at(2).get<int>();
                int plate_by = max(track_obj.rect.y, 0) + box.at(3).get<int>();
                // 前端截取车牌情况
                if (track_obj.car_plate_boxes.size() == 1) {
                    GCRect plate_rect = track_obj.car_plate_boxes[0]; // TODO
                    plate_lx = plate_rect.x;
                    plate_ty = plate_rect.y;
                    plate_rx = plate_lx + plate_rect.width;
                    plate_by = plate_ty + plate_rect.height;
                }
                GCTrackPlate track_plate;
                track_plate.track_obj = &track_obj;
                track_plate.plate_data = plate_data;
                track_plate.rect.x = plate_lx;
                track_plate.rect.y = plate_ty;
                track_plate.rect.width = plate_rx - plate_lx;
                track_plate.rect.height = plate_by - plate_ty;
                vector<int> cxby = { track_plate.rect.x + track_plate.rect.width / 2, track_plate.rect.y + track_plate.rect.height };
                track_plate.cxby = cxby;

                if (track_plate.cxby[1] < (track_obj.cxby[1] - track_obj.rect.height * 2 / 3)) {
                    is_all_frame_with_plate_and_not_move = false;

                    LOG_INFO("camera_key=%s, track_id=%d filter the plate on the top 1/3 of the car obj, plate=%s", m_camera_key.c_str(), m_track_id, plate.c_str());
                    continue;
                }

                track_plate.tlbr = { track_plate.rect.x, track_plate.rect.y, track_plate.rect.x + track_plate.rect.width, track_plate.rect.y + track_plate.rect.height };
                if (first_plate_tlbr.empty()) {
                    first_plate_tlbr = track_plate.tlbr;
                } else {
                    if (iou(first_plate_tlbr, track_plate.tlbr, 2) <= 0) {
                        is_all_frame_with_plate_and_not_move = false;
                    }
                }

                if (plate.empty()) {
                    continue;
                }
                
                if (plate_map.find(plate) == plate_map.end()) {
                    vector<GCTrackPlate> track_plates = { track_plate };
                    plate_map.insert({ plate, track_plates });
                } else {
                    plate_map.find(plate)->second.emplace_back(track_plate);
                }
            }
        }
        if (is_all_frame_with_plate_and_not_move) {
            m_track_result.inout = InoutType::INIT;
            LOG_INFO("camera_key=%s, track_id=%d reinit with all frame has plate and not move, change inout type to init", m_camera_key.c_str(), m_track_id);
            return;
        }
        if (plate_map.empty()) {
            LOG_INFO("camera_key=%s, track_id=%d end with no plate, inout=%d, m_track_end=%d", m_camera_key.c_str(), m_track_id, m_track_result.inout, m_track_end);
            return;
        }

        LOG_INFO("camera_key=%s, track_id=%d size of plate_map=%d before filter: ", m_camera_key.c_str(), m_track_id, plate_map.size());

        std::map<std::string, vector<GCTrackPlate>>::iterator iter;

        for (iter = plate_map.begin(); iter != plate_map.end(); iter++) {
            LOG_INFO("camera_key=%s, track_id=%d plate=%s", m_camera_key.c_str(), m_track_id, iter->first.c_str());
        }
        // if (plate_map.size() > 1) {
        //     // 0.1 过滤掉目标框外存在的车牌（检测框不准确时有一定概率会导致漏识别）；还有一种情况就是识别整图检测并识别了车牌但是识别车辆图时检测或识别失败
        //     std::vector<string> plates_outof_track_obj;
        //     for (auto &m_track_obj: m_track_objs) {
        //         if (m_track_obj.frame_lpr_result.empty() || m_track_obj.frame_lpr_result["errcode"] != 0) {
        //             continue;
        //         }
        //         nlohmann::json frame_plate_data_list = m_track_obj.frame_lpr_result["data"];
        //         if (frame_plate_data_list.empty()) {
        //             continue;
        //         }
        //         std::vector<std::string> plates_of_track_obj;
        //         if (!m_track_obj.lpr_result.empty() && m_track_obj.lpr_result["errcode"] != 0) {
        //             nlohmann::json plate_data_list = m_track_obj.lpr_result["data"];
        //             if (!plate_data_list.empty()) {
        //                 for (auto& plate_data : plate_data_list) {
        //                     std::string plate = plate_data["plate"];
        //                     if (plate.empty()) {
        //                         continue;
        //                     }
        //                     plates_of_track_obj.emplace_back(plate);
        //                 }
        //             }
        //         }
        //         for (auto& plate_data : frame_plate_data_list) {
        //             std::string plate = plate_data["plate"];
        //             if (plate.empty()) {
        //                 continue;
        //             }
        //             std::vector<std::string>::iterator iter = std::find(plates_of_track_obj.begin(), plates_of_track_obj.end(), plate);
        //             if (iter == plates_of_track_obj.end()) {
        //                 iter = std::find(plates_outof_track_obj.begin(), plates_outof_track_obj.end(), plate);
        //                 if (iter == plates_of_track_obj.end()) {
        //                     plates_outof_track_obj.emplace_back(plate);
        //                 }
        //             }
        //         }
        //     }

        //     if (!plates_outof_track_obj.empty()) {

        //         LOG_INFO("camera_key=%s, track_id=%d size of plates_outof_track_obj=%d: ", m_camera_key.c_str(), m_track_id, plates_outof_track_obj.size());

        //         for (auto &plate: plates_outof_track_obj) {
        //             LOG_INFO("camera_key=%s, track_id=%d plate=%s", m_camera_key.c_str(), m_track_id, plate.c_str());
        //         }
        //         auto ite = plate_map.end();
        //         for(iter = plate_map.begin(); iter != ite;) {
        //             if (std::find(plates_outof_track_obj.begin(), plates_outof_track_obj.end(), iter->first) != plates_outof_track_obj.end()) {
        //                 iter = plate_map.erase(iter);
        //             } else {
        //                 ++iter;
        //             }
        //         }
        //     }
        //     if (plate_map.empty()) {
        //         LOG_INFO("camera_key=%s, track_id=%d end with no plate after filter the outside of car's plate, inout=%d, m_track_end=%d", m_camera_key.c_str(), m_track_id, m_track_result.inout, m_track_end);
        //         return;
        //     }

        //     LOG_INFO("camera_key=%s, track_id=%d size of plate_map=%d after out plate filter: ", m_camera_key.c_str(), m_track_id, plate_map.size());
        // }

        // for (iter = plate_map.begin(); iter != plate_map.end(); iter++) {

        //     LOG_INFO("camera_key=%s, track_id=%d plate=%s", m_camera_key.c_str(), m_track_id, iter->first.c_str());

        //     for (auto &track_plate: iter->second) {
        //         LOG_INFO("\t\t\ttrack_id=%d plate_data=%s", m_track_id, track_plate.plate_data.dump().c_str());
        //     }
        // }

        std::pair<std::string, int> plate_count;
        // 取车牌
        // -. 从1-N条逻辑计算规则，顺序执行，任何一条返回则中断
        // 1. 如果只有一个车牌则直接确认结果，如同时底部中心点跨线，行为置信度=99.99%
        if (plate_map.size() == 1) {
            iter = plate_map.begin();
            std::string plate = iter->first;
            m_track_result.plate = plate;
            vector<GCTrackPlate> track_plate_list = iter->second;
            if (track_plate_list.size() > 1) {
                int start_pos_side = IsPointInPolygon(track_plate_list[0].cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1; // 0 矩形外；1 矩形内
                int end_pos_side = IsPointInPolygon(track_plate_list[track_plate_list.size() - 1].cxby, gc_app_config.camera_config_map[m_camera_key].roi) == 0 ? 0 : 1;
                if (start_pos_side != end_pos_side) {
                    InoutType plate_inout = start_pos_side == 0 ? InoutType::IN : InoutType::OUT;
                    if (plate_inout == m_track_result.inout) {
                        m_track_result.inout_conf = 99.99;
                    }
                }
                std::sort(track_plate_list.begin(), track_plate_list.end(), [](GCTrackPlate track_plate_a, GCTrackPlate track_plate_b) {
                    if (track_plate_b.plate_data["conf"].get<int>() > 95 && track_plate_a.plate_data["conf"].get<int>() > 95) {
                        // 根据车牌图片清晰度倒序根据车牌图片清晰度倒序(目前清晰度算法不靠谱)
                        // cv::Mat frame_img_a = GCImageCache::GetInstance().Get(track_plate_a.track_obj->img_path);
                        // cv::Mat img_plate_a = frame_img_a(cv::Rect(std::max(track_plate_a.rect.x, 0), std::max(track_plate_a.rect.y, 0), std::min(track_plate_a.rect.width, frame_img_a.cols - track_plate_a.rect.x), std::min(track_plate_a.rect.height, frame_img_a.rows - track_plate_a.rect.y)));

                        // cv::Mat frame_img_b = GCImageCache::GetInstance().Get(track_plate_b.track_obj->img_path);
                        // cv::Mat img_plate_b = frame_img_b(cv::Rect(std::max(track_plate_b.rect.x, 0), std::max(track_plate_b.rect.y, 0), std::min(track_plate_b.rect.width, frame_img_b.cols - track_plate_b.rect.x), std::min(track_plate_b.rect.height, frame_img_b.rows - track_plate_b.rect.y)));

                        // return get_laplacian_mean(img_plate_b) < get_laplacian_mean(img_plate_a);
                        // 根据车牌图片面积倒序
                        return track_plate_b.track_obj->car_plate_boxes[0].width * track_plate_b.track_obj->car_plate_boxes[0].height < track_plate_a.track_obj->car_plate_boxes[0].width * track_plate_a.track_obj->car_plate_boxes[0].height;
                    }
                    // 根据车牌置信度倒序
                    return track_plate_b.plate_data["conf"].get<int>() < track_plate_a.plate_data["conf"].get<int>();
                });               
            }
            GCTrackPlate track_plate = track_plate_list[0];
            m_track_result.plate_conf = track_plate.plate_data["conf"].get<int>();
            if (track_plate.plate_data["color"].is_null()) {
                m_track_result.plate_color = "";
            } else {
                m_track_result.plate_color = track_plate.plate_data["color"].get<std::string>();
            }
            // FIXME 因车牌存在一定的概率取到其他车牌，且中间件审核车牌和行为是分开的，会出问题，这里注释掉，不上传车牌截图
            if (strlen(track_plate.track_obj->img_path) == 0) {
                LOG_ERROR("track_id=%d img_path is empty", m_track_id);
                return;
            }
            cv::Mat frame_img;
            GCImageCache::GetInstance().Get(track_plate.track_obj->img_path, frame_img);
            if (frame_img.empty()) {
                LOG_ERROR("track_id=%d frame_img not found in cache for img_path=%s, size of IMG_CACHE=%d", m_track_id, track_plate.track_obj->img_path, IMG_CACHE.Size());
                return;
            }                
            m_track_result.plate_img = frame_img(cv::Rect(std::max(track_plate.rect.x, 0), std::max(track_plate.rect.y, 0), std::min(track_plate.rect.width, frame_img.cols - track_plate.rect.x), std::min(track_plate.rect.height, frame_img.rows - track_plate.rect.y)));

            LOG_INFO("camera_key=%s, track_id=%d only one plate=%s", m_camera_key.c_str(), m_track_id, plate.c_str());
            return;
        }

        // 2. 根据iou过滤相同位置车牌（已在追踪目标检测时进行过滤）
        // for (iter = plate_map.begin(); iter != plate_map.end(); iter++) {
        //     std::string plate = iter->first;
        //     vector<GCTrackPlate> track_plate_list = iter->second;
        //     vector<GCTrackPlate> new_track_plate_list;
        //     for (auto &track_plate: track_plate_list) {
        //         if (new_track_plate_list.empty()) {
        //             new_track_plate_list.emplace_back(track_plate);
        //             continue;
        //         }
        //         bool do_keep = true;
        //         for (auto &new_track_plate: new_track_plate_list) {
        //             float iou_v = iou(track_plate.tlbr, new_track_plate.tlbr, 2);
        //             if (iou_v > gc_app_config.base_config.alg_config.track_same_plate_iou_thresh) {
        //                 do_keep = false;
        //                 break;
        //             }
        //         }
        //         if (do_keep) {
        //             new_track_plate_list.emplace_back(track_plate);
        //         }
        //     }
        //     plate_map[plate] = new_track_plate_list;
        // }

        // LOG_INFO("camera_key=%s, track_id=%d plate_map after iou filter ----------", m_camera_key.c_str(), m_track_id);
        
        // for (iter = plate_map.begin(); iter != plate_map.end(); iter++) {
        //     LOG_INFO("camera_key=%s, track_id=%d plate=%s", m_camera_key.c_str(), m_track_id, iter->first.c_str());
        //     vector<GCTrackPlate> track_plate_list = iter->second;
        //     for (auto &track_plate: track_plate_list) {
        //         LOG_INFO("\t\t\ttrack_id=%d plate_data=%s", m_track_id, track_plate.plate_data.dump().c_str());
        //     }
        // }
        // 3. 合并相似车牌号（TODO 仅汉字不同车牌合并，以数量多的为准，待商榷）
        // 4. 计算每个车牌的计数，计数最多的为最终车牌
        int plate_max_cnt = 0;
        for (iter = plate_map.begin(); iter != plate_map.end(); iter++) {
            vector<GCTrackPlate> track_plate_list = iter->second;
            if (track_plate_list.size() >= plate_max_cnt) {
                plate_max_cnt = track_plate_list.size();
            }
        }
        vector<std::string> plate_with_max_count_list;
        iter = plate_map.begin();
        while ((iter = std::find_if(iter, plate_map.end(), [plate_max_cnt](const std::pair<std::string, vector<GCTrackPlate>> it) {
                return it.second.size() == plate_max_cnt;
            })) != plate_map.end())
        {
            plate_with_max_count_list.emplace_back(iter->first);
            iter++;
        }

        LOG_INFO("camera_key=%s, track_id=%d plate_with_max_count_list, plate_max_cnt=%d", m_camera_key.c_str(), m_track_id, plate_max_cnt);

        for (auto &plate: plate_with_max_count_list) {
            LOG_INFO("\t\t\t\ttrack_id=%d plate=%s", m_track_id, plate.c_str());
        }

        if (plate_with_max_count_list.size() == 1) {
            std::string plate = plate_with_max_count_list[0];
            vector<GCTrackPlate> track_plate_list = plate_map[plate];
            m_track_result.plate = plate;
            std::sort(track_plate_list.begin(), track_plate_list.end(), [](GCTrackPlate track_plate_a, GCTrackPlate track_plate_b) {
                if (track_plate_b.plate_data["conf"].get<int>() > 95 && track_plate_a.plate_data["conf"].get<int>() > 95) {
                    // 根据车牌图片清晰度倒序(目前清晰度算法不靠谱)
                    // cv::Mat frame_img_a = GCImageCache::GetInstance().Get(track_plate_a.track_obj->img_path);
                    // cv::Mat img_plate_a = frame_img_a(cv::Rect(std::max(track_plate_a.rect.x, 0), std::max(track_plate_a.rect.y, 0), std::min(track_plate_a.rect.width, frame_img_a.cols - track_plate_a.rect.x), std::min(track_plate_a.rect.height, frame_img_a.rows - track_plate_a.rect.y)));

                    // cv::Mat frame_img_b = GCImageCache::GetInstance().Get(track_plate_b.track_obj->img_path);
                    // cv::Mat img_plate_b = frame_img_b(cv::Rect(std::max(track_plate_b.rect.x, 0), std::max(track_plate_b.rect.y, 0), std::min(track_plate_b.rect.width, frame_img_b.cols - track_plate_b.rect.x), std::min(track_plate_b.rect.height, frame_img_b.rows - track_plate_b.rect.y)));

                    // return get_laplacian_mean(img_plate_b) < get_laplacian_mean(img_plate_a);
                    // 根据车牌图片面积倒序
                    return track_plate_b.track_obj->car_plate_boxes[0].width * track_plate_b.track_obj->car_plate_boxes[0].height < track_plate_a.track_obj->car_plate_boxes[0].width * track_plate_a.track_obj->car_plate_boxes[0].height;
                }
                // 根据车牌置信度倒序
                return track_plate_b.plate_data["conf"].get<int>() < track_plate_a.plate_data["conf"].get<int>();
            });
            GCTrackPlate track_plate = track_plate_list[0];
            m_track_result.plate_conf = track_plate.plate_data["conf"].get<int>();
            if (track_plate.plate_data["color"].is_null()) {
                m_track_result.plate_color = "";
            } else {
                m_track_result.plate_color = track_plate.plate_data["color"].get<std::string>();
            }
            if (plate_max_cnt > 1) {
                m_track_result.inout_conf = 95.f;
            } else {
                m_track_result.inout_conf = 90.f;
            }
            // FIXME 因车牌存在一定的概率取到其他车牌，且中间件审核车牌和行为是分开的，会出问题，这里注释掉，不上传车牌截图
            if (strlen(track_plate.track_obj->img_path) == 0) {
                LOG_ERROR("track_id=%d img_path is empty", m_track_id);
                return;
            }
            cv::Mat frame_img;
            GCImageCache::GetInstance().Get(track_plate.track_obj->img_path, frame_img);
            if (frame_img.empty()) {
                LOG_ERROR("track_id=%d frame_img not found in cache for img_path=%s, size of IMG_CACHE=%d", m_track_id, track_plate.track_obj->img_path, IMG_CACHE.Size());
                return;
            }
            LOG_INFO("camera_key=%s, track_id=%d xywh=%d,%d,%d,%d", m_camera_key.c_str(), m_track_id, track_plate.rect.x, track_plate.rect.y, track_plate.rect.width, track_plate.rect.height);
            m_track_result.plate_img = frame_img(cv::Rect(std::max(track_plate.rect.x, 0), std::max(track_plate.rect.y, 0), std::min(track_plate.rect.width, frame_img.cols - track_plate.rect.x), std::min(track_plate.rect.height, frame_img.rows - track_plate.rect.y)));

            LOG_INFO("camera_key=%s, track_id=%d only one plate with max cnt, plate=%s", m_camera_key.c_str(), m_track_id, plate.c_str());
            return;
        }
        /*
        废弃该规则
        // 5. 多个车牌计数相同取车牌y坐标较大的车牌（FIXME 待验证）
        int y_max = 0;
        std::string plate_with_y_max;
        for (auto &plate: plate_with_max_count_list) {
            vector<GCTrackPlate> track_plate_list = plate_map[plate];
            for (auto &track_plate: track_plate_list) {
                if (track_plate.cxby[1] > y_max) {
                    y_max = track_plate.cxby[1];
                    plate_with_y_max = plate;
                }
            }
        }
        */

        // 5. TODO 多个车牌计数相同取车牌离所在车辆检测框距离占比最小的，暂时取置信度最高的
        std::string plate_with_conf_max = plate_with_max_count_list[0];
        int max_conf = plate_map[plate_with_conf_max][0].plate_data["conf"].get<int>();
        for (auto &plate: plate_with_max_count_list) {
            if (plate_map[plate][0].plate_data["conf"].get<int>() > max_conf) {
                plate_with_conf_max = plate;
            }
        }
        
        vector<GCTrackPlate> track_plate_list = plate_map[plate_with_conf_max];
        m_track_result.plate = plate_with_conf_max;
        std::sort(track_plate_list.begin(), track_plate_list.end(), [](GCTrackPlate track_plate_a, GCTrackPlate track_plate_b) {
            if (track_plate_b.plate_data["conf"].get<int>() > 95 && track_plate_a.plate_data["conf"].get<int>() > 95) {
                // 根据车牌图片清晰度倒序根据车牌图片清晰度倒序(目前清晰度算法不靠谱)
                // cv::Mat frame_img_a = GCImageCache::GetInstance().Get(track_plate_a.track_obj->img_path);
                // cv::Mat img_plate_a = frame_img_a(cv::Rect(std::max(track_plate_a.rect.x, 0), std::max(track_plate_a.rect.y, 0), std::min(track_plate_a.rect.width, frame_img_a.cols - track_plate_a.rect.x), std::min(track_plate_a.rect.height, frame_img_a.rows - track_plate_a.rect.y)));

                // cv::Mat frame_img_b = GCImageCache::GetInstance().Get(track_plate_b.track_obj->img_path);
                // cv::Mat img_plate_b = frame_img_b(cv::Rect(std::max(track_plate_b.rect.x, 0), std::max(track_plate_b.rect.y, 0), std::min(track_plate_b.rect.width, frame_img_b.cols - track_plate_b.rect.x), std::min(track_plate_b.rect.height, frame_img_b.rows - track_plate_b.rect.y)));

                // return get_laplacian_mean(img_plate_b) < get_laplacian_mean(img_plate_a);
                // 根据车牌图片面积倒序
                return track_plate_b.track_obj->car_plate_boxes[0].width * track_plate_b.track_obj->car_plate_boxes[0].height < track_plate_a.track_obj->car_plate_boxes[0].width * track_plate_a.track_obj->car_plate_boxes[0].height;
            }
            // 根据车牌置信度倒序
            return track_plate_b.plate_data["conf"].get<int>() < track_plate_a.plate_data["conf"].get<int>();
        });
        GCTrackPlate track_plate = track_plate_list[0];
        m_track_result.plate_conf = track_plate.plate_data["conf"].get<int>();
        if (track_plate.plate_data["color"].is_null()) {
            m_track_result.plate_color = "";
        } else {
            m_track_result.plate_color = track_plate.plate_data["color"].get<std::string>();
        }
        // FIXME 因车牌存在一定的概率取到其他车牌，且中间件审核车牌和行为是分开的，会出问题，这里注释掉，不上传车牌截图
        if (strlen(track_plate.track_obj->img_path) == 0) {
            LOG_ERROR("track_id=%d img_path is empty", m_track_id);
            return;
        }
        cv::Mat frame_img;
        GCImageCache::GetInstance().Get(track_plate.track_obj->img_path, frame_img);
        if (frame_img.empty()) {
            LOG_ERROR("track_id=%d frame_img not found in cache for img_path=%s, size of IMG_CACHE=%d", m_track_id, track_plate.track_obj->img_path, IMG_CACHE.Size());
            return;
        }

        LOG_INFO("camera_key=%s, track_id=%d plate xywh=%d,%d,%d,%d", m_camera_key.c_str(), m_track_id, track_plate.rect.x, track_plate.rect.y, track_plate.rect.width, track_plate.rect.height);

        m_track_result.plate_img = frame_img(cv::Rect(std::max(track_plate.rect.x, 0), std::max(track_plate.rect.y, 0), std::min(track_plate.rect.width, frame_img.cols - track_plate.rect.x), std::min(track_plate.rect.height, frame_img.rows - track_plate.rect.y)));

        LOG_INFO("camera_key=%s, track_id=%d get max conf plate from plate with max cnt, plate=%s", m_camera_key.c_str(), m_track_id, plate_with_conf_max.c_str());
        
        return;
    }

    void InoutTrack::drawDebugInfo() {
        std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
        vector<GCTrackObj> proof_state_objs;
        proof_state_objs.insert(proof_state_objs.end(), std::make_move_iterator(m_track_result.proof_track_objs.begin()), std::make_move_iterator(m_track_result.proof_track_objs.end()));
        proof_state_objs.insert(proof_state_objs.end(), std::make_move_iterator(m_track_result.state_track_objs.begin()), std::make_move_iterator(m_track_result.state_track_objs.end()));

        // 只在证据图和状态图绘制调试信息
        for (auto &track_obj: proof_state_objs) {
            cv::Mat img;
            GCImageCache::GetInstance().Get(track_obj.img_path, img);
            if (img.empty()) {
                LOG_ERROR("track_id=%d frame_img not found in cache for img_path=%s, is_cross_frame=%d", m_track_id, track_obj.img_path, track_obj.is_cross_frame);
                continue;
            }
            vector<cv::Point2i> points;
            // roi
            if (gc_app_config.camera_config_map[m_camera_key].debug) {
                // roi
                for (int i = 0; i < gc_app_config.camera_config_map[m_camera_key].roi.size(); i += 2) {
                    points.emplace_back(cv::Point2i((int) (gc_app_config.camera_config_map[m_camera_key].roi[i]), (int) (gc_app_config.camera_config_map[m_camera_key].roi[i + 1])));
                }
                cv::polylines(img, {points}, true, cv::Scalar(0, 255, 255), 1, 8, 0);
            }
            // berthes
            // if (gc_app_config.camera_config_map[m_camera_key].debug) {
                // int idx = 3;
                for (auto &berth: gc_app_config.camera_config_map[m_camera_key].berthes) {
                    if (berth.name != m_track_result.berth) {
                        continue;
                    }
                    points.clear();
                    vector<int> x_list;
                    vector<int> y_list;
                    for (int i = 0; i < berth.roi.size(); i += 2) {
                        points.emplace_back((int) (berth.roi[i]), (int) (berth.roi[i + 1]));
                        x_list.emplace_back((int) (berth.roi[i]));
                        y_list.emplace_back((int) (berth.roi[i + 1]));
                    }
                    // cv::Scalar scalar = cv::Scalar(37 * idx % 255, 17 * idx % 255, 29 * idx % 255);
                    cv::Scalar scalar = cv::Scalar(0, 255, 0);
                    cv::polylines(img, {points}, true, scalar, 2, 8, 0);
                    // berth name
                    std::sort(x_list.begin(), x_list.end());
                    std::sort(y_list.begin(), y_list.end());
                    cv::Point2i name_point((x_list[1] + x_list[0]) / 2, (y_list[y_list.size() - 1] + y_list[0]) / 2);
                    if (gc_app_config.camera_config_map[m_camera_key].pos == GC_CAMERA_POS::POS_LEFT) {
                        name_point = cv::Point2i((x_list[x_list.size() - 1] + x_list[x_list.size() - 2]) / 2 - 150, (y_list[y_list.size() - 1] + y_list[0]) / 2);
                    }
                    cv::putText(img, berth.name, name_point, 3, 0.5, cv::Scalar(255, 255, 255), 3);
                    cv::putText(img, berth.name, name_point, 3, 0.5, cv::Scalar(0, 255, 0), 1);
                    // idx += 10;
                }
            // }
            // plate
            if (gc_app_config.camera_config_map[m_camera_key].debug || true) {
                GCRect rect = track_obj.rect;
                if (track_obj.lpr_result.empty() || track_obj.lpr_result["data"].empty() || track_obj.lpr_result["errcode"] != 0 ) {
                    LOG_INFO("camera_key=%s, track_id=%d lpr_result of track_obj is empty, %s", m_camera_key.c_str(), m_track_id, track_obj.lpr_result.dump().c_str());
                } else {
                    nlohmann::json plate_data_list = track_obj.lpr_result["data"];
                    for (auto& plate_data : plate_data_list) {
                        std::string plate = plate_data["plate"].get<std::string>();
                        if (plate.compare(m_track_result.plate.c_str()) == 0) {
                            nlohmann::json box = plate_data["box"];
                            // if (!plate_data["color"].is_null()) {
                            //     plate.append("-");
                            //     plate.append(plate_data["color"].get<std::string>());
                            // }                            
                            // plate.append("-");
                            // int conf = plate_data["conf"].get<int>();
                            // plate.append(to_string(conf));
                            int plate_lx = std::max(0, rect.x) + box.at(0).get<int>();
                            int plate_ty = std::max(0, rect.y) + box.at(1).get<int>();
                            int plate_rx = std::max(0, rect.x) + box.at(2).get<int>();
                            int plate_by = std::max(0, rect.y) + box.at(3).get<int>();
                            // 前端截取车牌情况
                            if (track_obj.car_plate_boxes.size() == 1) {
                                GCRect plate_rect = track_obj.car_plate_boxes[0]; // TODO
                                plate_lx = plate_rect.x;
                                plate_ty = plate_rect.y;
                                plate_rx = plate_lx + plate_rect.width;
                                plate_by = plate_ty + plate_rect.height;
                            }
                            cv::rectangle(img, cv::Point2i(plate_lx, plate_ty), cv::Point2i(plate_rx, plate_by), cv::Scalar(0, 0, 255), 1);
                            std::wstring w_plate = converter.from_bytes(m_track_result.plate_color + "_" + plate);
                            cv::Scalar font_size{ 26, 0.5, 0.1, 0 }; // (字体大小, 无效的, 字符间距, 无效的 }
                            CvxText::GetInstance().setFont(nullptr, &font_size, nullptr, 0);
                            CvxText::GetInstance().putText(img, w_plate.c_str(), cv::Point(plate_lx, plate_ty - 30), m_track_result.inout == InoutType::IN ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255));
                        }
                    }
                }
            }
            if (gc_app_config.camera_config_map[m_camera_key].debug) {
                // car rect by yolo detect
                for (auto &box: track_obj.car_boxes) {
                    cv::rectangle(img, cv::Point2i(box.x, box.y), cv::Point2i(box.x + box.width, box.y + box.height), cv::Scalar(255, 180, 170), 2);
                }
                // plate rect by yolo detect
                for (auto &box: track_obj.plate_boxes) {
                    // cv::rectangle(img, cv::Point2i(box.x, box.y), cv::Point2i(box.x + box.width, box.y + box.height), cv::Scalar(255, 180, 170), 2);
                    LandmarkInfo& landmark = box.landmark;
                    if (landmark.x0 > 0 || landmark.x2 > 0) {
                        cv::Point2i lm0((int)(landmark.x0), (int)(landmark.y0));
                        cv::Point2i lm1((int)(landmark.x1), (int)(landmark.y1));
                        cv::Point2i lm2((int)(landmark.x2), (int)(landmark.y2));
                        cv::Point2i lm3((int)(landmark.x3), (int)(landmark.y3));
                        cv::circle(img, lm0, 4, cv::Scalar(0, 0, 255), -1);
                        cv::circle(img, lm1, 4, cv::Scalar(0, 255, 0), -1);
                        cv::circle(img, lm2, 4, cv::Scalar(255, 0, 0), -1);
                        cv::circle(img, lm3, 4, cv::Scalar(0, 255, 255), -1);
                    }
                }
            }
            if (gc_app_config.camera_config_map[m_camera_key].debug || true) {
                // tracking car
                GCRect rect = track_obj.rect;
                cv::Point2i pt1(rect.x, rect.y);
                cv::Point2i pt2(rect.x + rect.width, rect.y + rect.height);
                cv::rectangle(img, pt1, pt2, cv::Scalar(255, 0, 0), 4);
                if (gc_app_config.camera_config_map[m_camera_key].debug) {
                    cv::circle(img, cv::Point2i(track_obj.cxby[0], track_obj.cxby[1]), 8, cv::Scalar(0, 0, 255), -1);

                    std::string car_conf = to_string(track_obj.track_id);
                    cv::Size text_size = cv::getTextSize(car_conf, 3, 0.6, 1, 0);
                    cv::rectangle(img, pt1, cv::Point2i(rect.x + text_size.width + 4, rect.y + text_size.height + 4), cv::Scalar(255, 0, 0), cv::FILLED);
                    cv::putText(img, car_conf, cv::Point2i(rect.x + 2, rect.y + text_size.height + 2), 3, 0.6, cv::Scalar(255, 255, 255), 1);
                }
            }
            compressImageWithROI(img, cv::Rect(track_obj.rect.x, track_obj.rect.y, track_obj.rect.width, track_obj.rect.height), 99, 8);
                        
            /*
            // track result
            // cv::rectangle(img, cv::Point2i(0, 0), cv::Point2i(gc_app_config.base_config.alg_config.subscribe_yuv_img_w, 20), cv::Scalar(255, 255, 255), cv::FILLED);

            // int baseline = 0; // 使用cv::getTextSize所必须传入的参数，但实际上未用到
            // std::string head_tip = "id=" + m_track_result.id.substr(m_track_result.id.size() - 7) + ", tid=" + to_string(track_obj.track_id) + ", " + to_string(i) + "/" + to_string(m_track_objs.size())
            //     +  ", i=" + to_string(m_track_result.inout) + ", p=" + m_track_result.plate + "-" + "-" + m_track_result.plate_color + to_string(m_track_result.plate_conf)
            //     + ", b=" + m_track_result.berth;
            std::string head_tip = gc_app_config.camera_config_map[m_camera_key].road + " " + track_obj.time_str + " " + m_track_result.plate_color + "-" + m_track_result.plate + " " + (m_track_result.inout == InoutType::IN ? "入场" : "离场") + ", 泊位" + m_track_result.berth + ", 可信度[" + to_string(std::lrint(m_track_result.inout_conf)) +  "%," + to_string(std::lrint(m_track_result.plate_conf)) + "%]";
            // std::string narrow = converter.to_bytes(wide_utf16_source_string);
            std::wstring w_head_tip = converter.from_bytes(head_tip);

            cv::Scalar font_size{ 26, 0.5, 0.1, 0 }; // (字体大小, 无效的, 字符间距, 无效的 }
            // CvxText::GetInstance().setFont(nullptr, &font_size, nullptr, 0);
            // CvxText::GetInstance().putText(img, w_head_tip.c_str(), cv::Point(2, 30), cv::Scalar(255, 255, 255));

            // font_size = cv::Scalar(24, 0.5, 0.1, 0);
            CvxText::GetInstance().setFont(nullptr, &font_size, nullptr, 0);
            CvxText::GetInstance().putText(img, w_head_tip.c_str(), cv::Point(2, 29), m_track_result.inout == InoutType::IN ? cv::Scalar(0, 255, 0) : cv::Scalar(0, 0, 255));

            // cv::putText(img, head_tip.c_str(), cv::Point2i(2, 16), 3, 0.6, cv::Scalar(255, 255, 255), 3);
            // cv::putText(img, head_tip.c_str(), cv::Point2i(2, 16), 3, 0.6, cv::Scalar(0, 255, 0), 1);

            // time and ip
            // cv::rectangle(img, cv::Point2i(0, gc_app_config.base_config.alg_config.subscribe_yuv_img_h - 20), cv::Point2i(gc_app_config.base_config.alg_config.subscribe_yuv_img_w, gc_app_config.base_config.alg_config.subscribe_yuv_img_h), cv::Scalar(255, 255, 255), cv::FILLED);

            std::string foot_str(gc_app_config.camera_config_map[m_camera_key].ip  + ", s=" + to_string(gc_app_config.camera_config_map[m_camera_key].state)
                + ", m=" + to_string(gc_app_config.camera_config_map[m_camera_key].model)
                + ", a=" + to_string(gc_app_config.camera_config_map[m_camera_key].alg_type)
                + ", ast: " + app_start_time);
            #ifdef BUILD_INFO
            foot_str += " " + std::string(BUILD_INFO);
            #endif
            foot_str += " id=" + m_track_result.id.substr(m_track_result.id.size() - 7) + ", tid=" + to_string(track_obj.track_id) + ", " + to_string(i) + "/" + to_string(m_track_objs.size());
            cv::putText(img, foot_str, cv::Point2i(2, gc_app_config.base_config.alg_config.subscribe_yuv_img_h - 4), 3, 0.6, cv::Scalar(255, 255, 255), 3);
            cv::putText(img, foot_str, cv::Point2i(2, gc_app_config.base_config.alg_config.subscribe_yuv_img_h - 4), 3, 0.6, cv::Scalar(0, 255, 0), 1);
            */

            cv::imwrite(track_obj.img_path, img);
        }
    }

    /**
     * @brief 
     * 
     * @param poi point,[x,y]
     * @param s_poi start point of edge,[x,y]
     * @return true 
     * @return false 
     */
    bool InoutTrack::IsConvertx(int poi[2], int s_poi[2])
    {
        // point in vertex or not
        return std::abs(poi[0] - s_poi[0]) < epsilon && std::abs(poi[1]-s_poi[1]) < epsilon;
    }

    /**
     * @brief 
     * 
     * @param poi point,[x,y]
     * @param s_poi start point of edge,[x,y]
     * @param e_poi end point of edge,[x,y]
     * @return true 
     * @return false 
     */
    bool InoutTrack::IsOnSegment(int poi[2], int s_poi[2], int e_poi[2])
    {
        int crossproduct = (poi[1] - s_poi[1]) * (e_poi[0] - s_poi[0]) - (poi[0] - s_poi[0]) * (e_poi[1] - s_poi[1]);
        // compare versus epsilon for floating point values, or != 0 if using integers
        if (std::abs(crossproduct) > epsilon) {
            return false;
        }
        int dotproduct = (poi[0] - s_poi[0]) * (e_poi[0] - s_poi[0]) + (poi[1] - s_poi[1])*(e_poi[1] - s_poi[1]);
        if (dotproduct < 0) {
            return false;
        }
        int squaredlengthba = (e_poi[0] - s_poi[0])*(e_poi[0] - s_poi[0]) + (e_poi[1] - s_poi[1])*(e_poi[1] - s_poi[1]);
        return dotproduct <= squaredlengthba;
    }

    /**
     * @brief Ray intersection is odd, in polygon, even out polygon
     * 
     * @param poi point,[x,y]
     * @param s_poi start point of edge,[x,y]
     * @param e_poi end point of edge,[x,y]
     * @return true 
     * @return false 
     */
    bool InoutTrack::IsRayIntersectsSegment(int poi[2], int s_poi[2], int e_poi[2])
    {
        if (std::abs(s_poi[1] - e_poi[1]) < epsilon) {  // exclude point parallel, coincide with edge, start && end point
            return false;
        }
        if (s_poi[1] < poi[1] && e_poi[1] < poi[1]) { //line top
            return false;
        }
        if (s_poi[1] > poi[1] && e_poi[1] > poi[1]) { //line bottom
            return false;
        }
        if (std::abs(s_poi[1] - poi[1]) < epsilon && e_poi[1] < poi[1]) { //line top, spoint
            return false;
        }
        if (std::abs(e_poi[1] - poi[1]) < epsilon && s_poi[1] < poi[1]) { //line top , epoint
            return false;
        }
        if (s_poi[0] < poi[0] && e_poi[0] < poi[0]) { //line left
            return false;
        }

        int xseg = e_poi[0]-(int) ((e_poi[0]-s_poi[0])*(e_poi[1]-poi[1]) * 1.f/((e_poi[1]-s_poi[1])));
        if (xseg < poi[0]) { //intersection left
            return false;
        }
        return true;  //exclude
    }

    /**
     * @brief 
     * 
     * @param poi point,[x,y]
     * @param polygon polygon points
     * @return int 0:out 1:in 2:on segment 3:on vertex
     */
    int InoutTrack::IsPointInPolygon(vector<int> point, vector<int> polygon)
    {
        if (polygon.size() == 0 || point.size() != 2) {
            return 0;
        }
        int result = 0;
        int poi[2] = { point[0], point[1] };
        // poly = [x1,y1,x2,y2,...,xn,yn,...,x1,y1]
        vector<int> poly(polygon);
        poly.emplace_back(poly[0]);
        poly.emplace_back(poly[1]);

        int sinsc = 0;
        // for every line [x1,y1,…xn,yn]
        for (int i = 0; i < poly.size() - 3; i+=2) {
            int s_poi[2] = { poly[i], poly[i+1] };
            int e_poi[2] = { poly[i+2], poly[i+3] };
            if(IsConvertx(poi, s_poi)) {
                result = 3;
                break;
            }
            if (IsOnSegment(poi, s_poi, e_poi)) {
                result = 2;
                break;
            }
            if (IsRayIntersectsSegment(poi, s_poi, e_poi)) {
                sinsc += 1;
            }
        }
        if (sinsc % 2 == 1) {
            result = 1;
        } else {
            result = 0;
        }

        return result;     
    }
}